/***********************************************************************
qtvideo.m - Creates a QuickTime video file from a set of raw
	TIFF frames.

 Created 2006.06.18 by Warren Young; based on example code in

 http://developer.apple.com/technotes/tn2005/tn2138.html

	and

 http://developer.apple.com/samplecode/QTKitCreateMovie/

 Copyright (c) 2006 by Pixel Corps.  This program may be used under
 the terms of the X11 license, a copy of which should have accompanied
 this program, in the LICENSE file.
***********************************************************************/

#include "qtvideo.h"

#include <AppKit/AppKit.h>
#include <CoreFoundation/CoreFoundation.h>

#include <unistd.h>
#include <glob.h>


////////////////////////////////////////////////////////////////////////
// Globals

// Reference to low-level QT data I/O handler object.
static DataHandler gOutDataHandler;

// Name of temporary file used in building movie
static char gTempFileName[100];


//// CreateTemporaryMovieFile //////////////////////////////////////////
// Create empty movie file in temp directory.

static BOOL
CreateTemporaryMovieFile()
{
	int len = sizeof(gTempFileName);
	strncpy(gTempFileName, "/tmp/qtassy.XXXXXXXX", len);
	gTempFileName[len - 1] = '\0';
	
	int fd = mkstemp(gTempFileName);
	if (fd >= 0) {
		close(fd);
		return YES;
	}
	else {
		NSLog(@"Failed to create temporary movie \"%s\": %s\n",
				gTempFileName, sys_errlist[errno]);
		return NO;
	}
}


//// CreateEncodingParameters //////////////////////////////////////////
// Create encoding parameter dictionary

static BOOL
CreateEncodingParameters(NSDictionary** ppEncParms)
{
	*ppEncParms = [NSDictionary dictionaryWithObjectsAndKeys:
			[NSNumber numberWithBool:YES], QTMovieExport,
			[NSNumber numberWithLong:kH264CodecType],
				QTMovieExportType,
			@"avc1", QTAddImageCodecType,
			[NSNumber numberWithLong:codecHighQuality],
				QTAddImageCodecQuality,
			[NSNumber numberWithBool:YES], QTMovieFlatten,
			nil];
	return YES;
}


//// CreateMovieObject /////////////////////////////////////////////////
// Create QTKit movie object suitable for adding raw frames to, stored
// in the temporary file named in gTempFileName.  The temporary file
// name _does_ have to be different from the final rendered QT movie
// file, or else the QT mechanism gets horribly confused.

static BOOL
CreateMovieObject(QTMovie** ppMovie, DataHandler* outDataHandler)
{
	Handle dataRefH = nil;
	OSType dataRefType;
	Movie qtMovie = nil;
	
	// Create a writeable native QT movie object
	NSString* nsFileName =
			[NSString stringWithUTF8String:gTempFileName];
	OSErr err = QTNewDataReferenceFromFullPathCFString(
			(CFStringRef)nsFileName, kQTNativeDefaultPathStyle, 0,
			&dataRefH, &dataRefType);
	if (err == noErr) {
		// Set up storage management for the new QT movie
		err = CreateMovieStorage(dataRefH, dataRefType, 'TVOD',
				smSystemScript, newMovieActive, outDataHandler,
				&qtMovie);
		if (err == noErr) {
			// Convert native QT movie just created to QTKit object
			*ppMovie = [QTMovie movieWithQuickTimeMovie:qtMovie
					disposeWhenDone:YES error:nil];
			if (*ppMovie) {
				// Make the movie editable
				[*ppMovie setAttribute:[NSNumber numberWithBool:YES]
						forKey:QTMovieEditableAttribute];
				return YES;
			}
			else {
				NSLog(@"Failed to create QTKit movie from native.\n");
				return NO;
			}
		}
		else {
			NSLog(@"Failed to create storage for QT movie!\n");
			return NO;
		}
	}		
	else {
		NSLog(@"Failed to create native QT movie data reference!\n");
		return NO;
	}
}


//// AddImages /////////////////////////////////////////////////////////
// Add images to the QT movie

static BOOL
AddImages(QTMovie* movie, NSString* fileSpec, NSDictionary* encparms)
{
	glob_t files;
	if (glob([fileSpec UTF8String], GLOB_NOESCAPE, 0, &files) == 0) {
		QTTime dur = QTMakeTime(20, 600);
		for (int i = 0; i < files.gl_pathc; ++i) {
			NSString* fn =
			[NSString stringWithUTF8String:files.gl_pathv[i]];
			NSImage* image = [[NSImage alloc]
					initWithContentsOfFile:fn];
			if (!image) {
				NSLog(@"Failed to load secondary image!\n");
				return NO;
			}
			
			[movie addImage:image forDuration:dur
			 withAttributes:encparms];
			NSLog(@"Added %@...\n", fn);
		}
		
		globfree(&files);
		return YES;
	}
	else {
		NSLog(@"Failed to match \"%@\"\n", fileSpec);
		return NO;
	}
}


//// WriteMovieFile ////////////////////////////////////////////////////
// Save the QT movie to disk

static BOOL
WriteMovieFile(QTMovie* movie, NSString* fileName,
		NSDictionary* encparms)
{
	return [movie writeToFile:fileName withAttributes:encparms];
}


//// CloseMovie ////////////////////////////////////////////////////////

BOOL
CloseMovie()
{
	return (CloseMovieStorage(gOutDataHandler) == noErr) &&
			(unlink(gTempFileName) == 0);
}


///// CreateVideoTrack /////////////////////////////////////////////////
// Call the above functions in the appropriate order.

BOOL
CreateVideoTrack(QTMovie** ppMovie, NSString* movieFileName,
		NSString* fileSpec)
{
	NSDictionary* encParms;

	return CreateTemporaryMovieFile() &&	
			CreateEncodingParameters(&encParms) &&
			CreateMovieObject(ppMovie, &gOutDataHandler) &&
			AddImages(*ppMovie, fileSpec, encParms) &&
			WriteMovieFile(*ppMovie, movieFileName, encParms);
}
